/**
 * DefaultDababaseIndexSource.java	1.0 2007 五月 29 15:12:38,创建
 */

package org.in4j.search.index;

import java.io.File;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.dom4j.Element;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.builder.ToStringBuilder;

/**
 *
 * @author 杨帆
 * @version 1.0 2007 五月 29 15:12:38,创建
 */
public class DefaultDababaseIndexSource implements IndexSource
{
	@SuppressWarnings("unused")
	private static final Log log = LogFactory.getLog(DefaultDababaseIndexSource.class);

	private String driverClassName;

	private String url;

	private String username;

	private String password;

	private List<TableConfig> tables = new ArrayList<TableConfig>();

	private String indexDirectory;

	private Analyzer analyzer;

	private String id;

	public void buildIndex()
	{
		try
		{
			addDocumentsToWriter();
		}
		catch(RuntimeException ex)
		{
			throw ex;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	private void addDocumentsToWriter()
	{
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		boolean success = false;
		try
		{
			conn = getConncetion();
			stmt = conn.createStatement();

			final DatabaseMetaData metaData = conn.getMetaData();
			for(TableConfig table : tables)
			{
				String dirString = getIndexDirForTable(table.getName());
				log.debug("index-dir==>" + dirString);
				final Directory dir = FSDirectory.getDirectory(dirString, true);
				final IndexWriter writer = new IndexWriter(dir,getAnalyzer(),true);

				Set<String> columns = table.getColumns(metaData);
				final String sql = buildSql(columns,table);
				System.out.println("sql==>" + sql);
				rs = stmt.executeQuery(sql);
				while(rs.next())
				{
					Document doc = createDocument(rs,table);
					writer.addDocument(doc);
				}
				writer.optimize();
				writer.close();
			}

			success = true;
		}
		catch(Exception ex)
		{
			throw new RuntimeException("建立索引失败.",ex);
		}
		finally
		{
			if(success) commit(conn);
			else rollback(conn);
			close(rs);
			close(stmt);
			close(conn);
		}
	}


	public String getIndexDirForTable(String tableName)
	{
		String dir = getIndexDirectory();
		if(!dir.endsWith("/") && !dir.endsWith("\\")) dir += File.separator;
		dir += tableName;
		return dir;
	}

	private Document createDocument(ResultSet rs, TableConfig table) throws SQLException
	{
		final Document doc = new Document();
		final SimpleDateFormat format = new SimpleDateFormat();
		final String absoluteTableName = getId() + "." + table.getName();

		ResultSetMetaData rsmd = rs.getMetaData();
		final int count = rsmd.getColumnCount();

		StringBuilder fields = new StringBuilder();
		for(int i = 1; i <= count; i++)
		{
			String columnName = rsmd.getColumnName(i).toLowerCase();
			FieldConfig fieldConfig = table.getFieldConfig(columnName);
			if(fieldConfig == null) fieldConfig = FieldConfig.DEFAULT;
			String value;
			if("date".equals(fieldConfig.getFieldType()) && fieldConfig.getDatePattern() != null)
			{
				format.applyPattern(fieldConfig.getDatePattern());
				final int sqlType = rsmd.getColumnType(i);
				if(sqlType == Types.TIMESTAMP)
				{
					Timestamp timestamp = rs.getTimestamp(i);
					value = format.format(timestamp);
				}
				else if(sqlType == Types.TIME)
				{
					Time time = rs.getTime(i);
					value = format.format(time);
				}
				else if(sqlType == Types.DATE)
				{
					Date date = rs.getDate(i);
					value = format.format(date);
				}
				else
				{
					throw new RuntimeException("配置不正确,字段 " + columnName + "不是日期类型");
//					value = rs.getString(i);
				}
			}
			else
			{
				value = rs.getString(i);
			}
			if(value == null) value = "";

			final String absoluteFieldName = absoluteTableName + "." + columnName;
			if(i > 1) fields.append(",");
			fields.append(absoluteFieldName);
//			log.debug("\n" + absoluteFieldName + "==>" + value);
			//Field field = fieldBuilder.buildField(absoluteFieldName, value, fieldConfig.getStore(), fieldConfig.getIndex());
			Field field = new Field(absoluteFieldName,value,Field.Store.YES,Field.Index.TOKENIZED);
			doc.add(field);
//			String indexType = fieldConfig.getIndexType();
//			FieldBuilder fieldBuilder = getFieldCreatorByType(indexType);
			//Field field = fieldBuilder.buildField(absoluteFieldName, value, fieldConfig.getStore(), fieldConfig.getIndex());
		}
		Field metaField = new Field("fields",fields.toString(),Field.Store.YES,Field.Index.UN_TOKENIZED);
		doc.add(metaField);
		Field tableField = new Field("table",absoluteTableName,Field.Store.YES,Field.Index.UN_TOKENIZED);
		doc.add(tableField);
		return doc;
	}

	private String buildSql(Set<String> columns,TableConfig table)
	{
		StringBuilder sql = new StringBuilder(255);
		sql.append("SELECT ");
		boolean isNotFirst = false;
		for(String column : columns)
		{
			if(isNotFirst)
				sql.append(",");
			sql.append(column);
			isNotFirst = true;
		}
		sql.append(" FROM " + table.getName());
		if(table.getWhere() != null)
		{
			sql.append(" WHERE " + table.getWhere());
		}
		return sql.toString();
	}

	public void init(Element element)
	{
		this.id = element.attributeValue("id");
		this.indexDirectory = element.attributeValue("index-directory");
		this.analyzer = buildObject(element.attributeValue("analyzer"));
		this.driverClassName = element.selectSingleNode("./properties/property[@name='driverClassName']").getText();
		this.url = element.selectSingleNode("./properties/property[@name='url']").getText();
		this.username = element.selectSingleNode("./properties/property[@name='username']").getText();
		this.password = element.selectSingleNode("./properties/property[@name='password']").getText();
		for(Iterator<Element> iter = element.elementIterator("table"); iter.hasNext(); )
		{
			Element node = iter.next();
			TableConfig tableConfig = new TableConfig();
			String name = node.attributeValue("name");
			float boost = Float.valueOf(node.attributeValue("boost","1.0"));
			tableConfig.setName(name);
			tableConfig.setBoost(boost);

			setFields(tableConfig,node.element("field-config"));
			setExcludeFields(tableConfig,node.element("exclude"));
			setWhere(tableConfig,node.element("where"));
			setFilter(tableConfig,node.element("filter"));

			this.tables.add(tableConfig);
		}
	}

	private Analyzer buildObject(String className)
	{
		try
		{
			return (Analyzer)Class.forName(className).newInstance();
		}
		catch(Exception e)
		{
			throw new RuntimeException("分词器配置错误.",e);
		}
	}

	private void setFields(TableConfig tableConfig, Element element)
	{
		for(Iterator<Element> iter = element.elementIterator("field"); iter.hasNext(); )
		{
			Element node = iter.next();
			FieldConfig fieldConfig = new FieldConfig();
			String name = node.attributeValue("name");
			float boost = Float.valueOf(node.attributeValue("boost","1.0"));
			String fieldType = node.attributeValue("field-type");

			String store = node.attributeValue("lucene-store","YES").toUpperCase();
			String index = node.attributeValue("lucene-index","TOKENIZED").toUpperCase();
			String datePattern = node.attributeValue("date-pattern");
			fieldConfig.setName(name);
			fieldConfig.setStore(FieldConfig.Store.valueOf(store));
			fieldConfig.setIndex(FieldConfig.Index.valueOf(index));
			fieldConfig.setBoost(boost);

			fieldConfig.setFieldType(fieldType);
			fieldConfig.setDatePattern(datePattern);

			tableConfig.addFieldConfig(fieldConfig);
		}
	}

	private void setExcludeFields(TableConfig tableConfig, Element element)
	{
		if(element == null) return;
		String str = element.getText();
		String[] fields = str.split(",");
		for(String field : fields)
		{
			tableConfig.addExcludeField(field);
		}
	}

	private void setWhere(TableConfig tableConfig, Element element)
	{
		if(element == null) return;
		String where = element.getText();
		tableConfig.setWhere(where);
	}

	private void setFilter(TableConfig tableConfig, Element element)
	{
		if(element == null) return;
		if(true)
			throw new UnsupportedOperationException("尚未实现此功能.");
	}

	public Analyzer getAnalyzer()
	{
		return analyzer;
	}

	public void setAnalyzer(Analyzer analyzer)
	{
		this.analyzer = analyzer;
	}

	public String getDriverClassName()
	{
		return driverClassName;
	}

	public void setDriverClassName(String driverClassName)
	{
		this.driverClassName = driverClassName;
	}

	public String getIndexDirectory()
	{
		return indexDirectory;
	}

	public void setIndexDirectory(String indexDirectory)
	{
		this.indexDirectory = indexDirectory;
	}

	public String getPassword()
	{
		return password;
	}

	public void setPassword(String password)
	{
		this.password = password;
	}

	public String getUrl()
	{
		return url;
	}

	public void setUrl(String url)
	{
		this.url = url;
	}

	public String getUsername()
	{
		return username;
	}

	public void setUsername(String username)
	{
		this.username = username;
	}

	public List<TableConfig> getTables()
	{
		return tables;
	}

	private void rollback(Connection conn)
	{
		try
		{
			if(conn != null)
				conn.rollback();
		}
		catch(SQLException e)
		{
			e.printStackTrace();
		}
	}

	private void commit(Connection conn)
	{
		try
		{
			if(conn != null)
				conn.commit();
		}
		catch(SQLException e)
		{
			e.printStackTrace();
		}
	}

	private void close(Connection conn)
	{
		try
		{
			if(conn != null)
				conn.close();
		}
		catch(SQLException e)
		{
			e.printStackTrace();
		}
	}

	private Connection getConncetion()
	{
		try
		{
			Class.forName(getDriverClassName());
			Connection conn = DriverManager.getConnection(getUrl(),getUsername(),getPassword());
			return conn;
		}
		catch(Exception e)
		{
			throw new RuntimeException("无法获取数据库连接,不能生成索引.",e);
		}
	}

	private void close(Statement stmt)
	{
		try
		{
			if(stmt != null)
				stmt.close();
		}
		catch(SQLException e)
		{
			e.printStackTrace();
		}
	}

	private void close(ResultSet rs)
	{
		try
		{
			if(rs != null)
				rs.close();
		}
		catch(SQLException e)
		{
			e.printStackTrace();
		}
	}

	public String getId()
	{
		return id;
	}

	@Override
	public String toString()
	{
		return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("id", id).toString();
	}


}
